/**
 * 
 */
package truerefactor;

import java.io.File;
import java.util.Calendar;
import java.util.List;

import truerefactor.error.SystemControllerException;
import truerefactor.genetic.GAParameters;
import truerefactor.graph.CodeGraph;
import truerefactor.graph.GraphPool;
import truerefactor.input.CPPGraphParser;
import truerefactor.input.CodeGraphBuilder;
import truerefactor.input.JavaGraphParser;
import truerefactor.output.OutputDirector;
import truerefactor.output.UMLGenerator;

/**
 * SystemController - Acts as both the main hub for passing information between
 * the user and the system, as well as the upper level manager for system
 * operation
 * 
 * @author Isaac Griffith
 */
public class SystemController implements Runnable {

    /**
     * the refactoring controller which directs the refactoring process
     */
    private RefactoringController controller;
    /**
     * the output director which generates all output files
     */
    private OutputDirector outputDirector;
    /**
     * the output location for log files
     */
    private String logFileLoc;
    /**
     * the output location for UML diagrams
     */
    private String umlOutLoc;
    /**
     * the root directory to write generated code to
     */
    private String codeOutLoc;
    /**
     * the root directory of code to be parsed
     */
    private String codeInLoc;
    /**
     * the current language for the parser
     */
    private String language;
    /**
     * the current code graph being manipulated
     */
    private CodeGraph graph;
    /**
     * Parent UI which this controller interfaces the rest of the system with
     */
    private TrueRefactorGUI parent;
    /**
     * The max number of generations for a given run of the Genetic Algorithm
     */
    private int numGenerations;

    /**
     * Creates a new SystemController with the provided parent UI
     * 
     * @param parent
     *            The parent GUI
     */
    public SystemController(TrueRefactorGUI parent)
    {
        logFileLoc = "";
        umlOutLoc = "";
        codeOutLoc = "";
        codeInLoc = "";
        language = "";
        numGenerations = 100;
        graph = new CodeGraph();
        controller = new RefactoringController(this, graph);
        outputDirector = null;
        this.parent = parent;
    }

    /**
     * Begins the process of refactoring and output generation
     */
    public void process()
    {
        outputDirector = new OutputDirector(new UMLGenerator(graph), getLogFile(""), getLogFile("Fitness-Log"),
                getLogFile("CodeSmell-Log"));

        CodeGraphBuilder graphBuilder = this.selectParser();

        if (graphBuilder == null)
            return;

        graphBuilder.process(graph, new File(codeInLoc));
        GraphPool.getInstance().setInitialGraph(graph);

        // generate the initial results
        // generate the initial UML diagram and display it

        outputDirector.constructOutput(graph, umlOutLoc, "Initial");

        System.out.println("Size of graph: Nodes: " + graph.numberOfNodes() + " Edges: " + graph.numberOfEdges());
        GAParameters params = new GAParameters(controller.getAvailableMetrics().size(), parent.getMaxLogLength(),
                parent.getMaxIterations(), parent.getMutationRate(), parent.getPopSize(), parent.getSmellFactor(),
                graphBuilder.getTotalLOC());
        System.out.println("Total LOC: " + graphBuilder.getTotalLOC());
        System.out.println("GA Params:\n" + params.toString());

        controller.start(graph, params);
    }

    /**
     * Selects the parser to be used
     */
    private CodeGraphBuilder selectParser()
    {
        if (language == null)
        {
            try
            {
                deriveLanguage();
            }
            catch (SystemControllerException e)
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        if (language.equalsIgnoreCase(JavaGraphParser.LANGUAGE))
        {
            return new CodeGraphBuilder(new JavaGraphParser());
        }
        else if (language.equalsIgnoreCase(CPPGraphParser.LANGUAGE))
        {
            return new CodeGraphBuilder(new CPPGraphParser());
        }

        return null;
    }

    /**
     * Attempts to derive the language from the file types in a directory.
     * Currently this does nothing but throw an exception
     * 
     * @throws SystemControllerException
     */
    private void deriveLanguage() throws SystemControllerException
    {
        // look at files in directory and determine based on
        // file extensions what language is being employed

        // if no language is assigned, because of a bad directory
        // location or file problems, then throw an exception
        if (language == null)
        {
            throw new SystemControllerException(
                    "A Lanaguage was not provided, and one could not be derived. Processing therefore has failed.");
        }
    }

    /**
     * @return the current outputDirector
     */
    public OutputDirector getOutputDirector()
    {
        return outputDirector;
    }

    /**
     * Sets the output director to be used
     * 
     * @param outputDirector
     *            the outputDirector to set
     */
    public void setOutputDirector(OutputDirector outputDirector)
    {
        this.outputDirector = outputDirector;
    }

    /**
     * @return the directory in which log files will be written
     */
    public String getLogFileLoc()
    {
        return logFileLoc;
    }

    /**
     * Sets the directory in which log files will be written
     * 
     * @param logFileLoc
     *            the logFileLoc to set
     */
    public void setLogFileLoc(String logFileLoc)
    {
        this.logFileLoc = logFileLoc;
    }

    /**
     * @return the directory in which UML files will be written
     */
    public String getUmlOutLoc()
    {
        return umlOutLoc;
    }

    /**
     * Sets the directory to which UML files will be written
     * 
     * @param umlOutLoc
     *            the umlOutLoc to set
     */
    public void setUmlOutLoc(String umlOutLoc)
    {
        this.umlOutLoc = umlOutLoc;
    }

    /**
     * @return the root directory for code generation
     */
    public String getCodeOutLoc()
    {
        return codeOutLoc;
    }

    /**
     * Sets the location of the root directory in which code is to be generated
     * in.
     * 
     * @param codeOutLoc
     *            the codeOutLoc to set
     */
    public void setCodeOutLoc(String codeOutLoc)
    {
        this.codeOutLoc = codeOutLoc;
    }

    /**
     * @return the root directory of the code base to be parsed
     */
    public String getCodeInLoc()
    {
        return codeInLoc;
    }

    /**
     * Sets the root directory of the code base to be parsed to be the string
     * provided
     * 
     * @param codeInLoc
     *            the codeInLoc to set
     */
    public void setCodeInLoc(String codeInLoc)
    {
        this.codeInLoc = codeInLoc;
    }

    /**
     * @return the current language in use
     */
    public String getLanguage()
    {
        return language;
    }

    /**
     * Sets the current language to be the one provided
     * 
     * @param language
     *            the language to set
     */
    public void setLanguage(String language)
    {
        this.language = language;
    }

    /**
     * @return a list of names of available metrics that can be measured
     */
    public List<String> getAvailableMetrics()
    {
        return controller.getAvailableMetrics();
    }

    /**
     * @return A list of names of available refactorings that can be utilized
     */
    public List<String> getAvailableRefactorings()
    {
        return controller.getAvailableRefactorings();
    }

    /**
     * @return returns a list of names of available code smells that can be
     *         measured
     */
    public List<String> getAvailableCodeSmells()
    {
        return controller.getAvailableCodeSmells();
    }

    /**
     * Notifies the System Controller that refactoring is complete.
     * 
     * @param logUpdate
     *            last update to the log display
     * @param best
     *            code graph evaluated to be the best
     */
    public void refactoringComplete(String logUpdate, CodeGraph best)
    {
        updateLog(logUpdate);
        graph = best;
        outputDirector.constructOutput(best, umlOutLoc, "Final");
        outputDirector.constructResultsReport(parent.getLog());
    }

    /**
     * Updates the log display with the provided string
     * 
     * @param log
     *            string to append to the log display
     */
    public synchronized void updateLog(String log)
    {
        parent.addToLogger(log);
    }

    /**
     * Sets the current code graph to be the provided code graph
     * 
     * @param graph
     *            new current code graph
     */
    public void setGraph(CodeGraph graph)
    {
        this.graph = graph;
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Runnable#run()
     */
    public void run()
    {
        process();
    }

    /**
     * purges the log output panel in the UI
     */
    public void purgeLog()
    {
        outputDirector.constructResultsReport(parent.getLog());
        parent.setLog("");
    }

    /**
     * Generates a File representation of the log file with the given appended
     * text in the file name
     * 
     * @param append
     *            String to append the generated portion of the name to
     * @return the complete file name represented as a File object
     */
    public File getLogFile(String append)
    {
        String file = "";
        if (logFileLoc.equals(""))
        {
            StringBuilder sb = new StringBuilder();

            if (umlOutLoc.endsWith(System.getProperty("file.separator")))
            {
                sb.append(umlOutLoc);
            }
            else
            {
                sb.append(umlOutLoc);
                sb.append(System.getProperty("file.separator"));
            }

            getFileName(sb);

            if (append.length() > 1)
            {
                sb.append("-");
                sb.append(append);
            }

            sb.append(".txt");
            file = sb.toString();
        }
        else
        {
            File temp = new File(logFileLoc);
            if (temp.isDirectory())
            {
                StringBuilder sb = new StringBuilder();
                if (logFileLoc.endsWith(System.getProperty("file.separator")))
                {
                    sb.append(logFileLoc);
                }
                else
                {
                    sb.append(logFileLoc);
                    sb.append(System.getProperty("file.separator"));
                }

                getFileName(sb);

                if (append.length() > 1)
                {
                    sb.append("-");
                    sb.append(append);
                }

                sb.append(".txt");
                file = sb.toString();
            }
            else
            {
                file = logFileLoc;
            }
        }
        return new File(file);
    }

    /**
     * @param valueOf
     *            Sets the max number of GA Iterations
     */
    public void setNumberGenerations(int valueOf)
    {
        numGenerations = valueOf;
    }

    /**
     * @return maximum number of GA Iterations
     */
    public int getNumberGenerations()
    {
        return numGenerations;
    }

    /**
     * Records fitness information into the log panel of the GUI
     * 
     * @param generation
     *            current GA Iteration
     * @param bestVal
     *            total value
     * @param bestVals
     *            component values
     */
    public void recordFitnessLog(int generation, double bestVal, double[] bestVals)
    {
        outputDirector.recordFitness(generation, bestVal, bestVals);
    }

    /**
     * Records code smell information into the log panel of the GUI
     * 
     * @param generation
     *            current GA iteration
     * @param smell
     *            total of the code smell
     * @param smells
     *            code smell component values
     */
    public void recordSmellLog(int generation, double smell, double[] smells)
    {
        outputDirector.recordCodeSmell(generation, smell, smells);
    }

    /**
     * Returns a unique file name
     * 
     * @param sb
     *            StringBuilder containing the initial path of the file name
     */
    private void getFileName(StringBuilder sb)
    {
        Calendar cal = Calendar.getInstance();

        sb.append("Log-");
        sb.append(cal.get(Calendar.DAY_OF_MONTH));
        sb.append(cal.get(Calendar.MONTH) + 1);
        sb.append(cal.get(Calendar.YEAR));
        sb.append("_");
        sb.append(cal.get(Calendar.HOUR_OF_DAY));
        sb.append(cal.get(Calendar.MINUTE));
    }
}
